/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.logging;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * <p>
 * Factory for creating {@link Log} instances, with discovery and configuration features similar to that employed by standard Java APIs such as JAXP.
 * </p>
 * 
 * <p>
 * <strong>IMPLEMENTATION NOTE</strong> - This implementation is heavily based on the SAXParserFactory and DocumentBuilderFactory implementations
 * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
 * </p>
 * 
 * @author Nicolas Dos Santos
 */
public abstract class AndroidLogFactory {

    // ----------------------------------------------------- Manifest Constants

    /**
     * The name (<code>priority</code>) of the key in the config file used to specify the priority of that particular config file. The associated
     * value is a floating-point number; higher values take priority over lower values.
     */
    public static final String PRIORITY_KEY = "priority";

    /**
     * The name (<code>org.apache.commons.logging.LogFactory</code>) of the property used to identify the AndroidLogFactory implementation class name.
     * This can be used as a system property, or as an entry in a configuration properties file.
     */
    public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";

    /**
     * The fully qualified class name of the fallback <code>AndroidLogFactory</code> implementation class to use, if no other can be found.
     */
    public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.AndroidLogFactoryImpl";

    /**
     * The name (<code>android-commons-logging.properties</code>) of the properties file to search for.
     */
    public static final String FACTORY_PROPERTIES = "android-commons-logging.properties";

    /**
     * The name (<code>org.apache.commons.logging.diagnostics.dest</code>) of the property used to enable internal commons-logging diagnostic output,
     * in order to get information on what logging implementations are being discovered, what classloaders they are loaded through, etc.
     * <p>
     * If a system property of this name is set then the value is assumed to be the name of a file. The special strings STDOUT or STDERR
     * (case-sensitive) indicate output to System.out and System.err respectively.
     * <p>
     * Diagnostic logging should be used only to debug problematic configurations and should not be set in normal production use.
     */
    public static final String DIAGNOSTICS_DEST_PROPERTY = "org.apache.commons.logging.diagnostics.dest";

    /**
     * When null (the usual case), no diagnostic output will be generated by AndroidLogFactory or AndroidLogFactoryImpl. When non-null, interesting
     * events will be written to the specified object.
     */
    private static PrintStream diagnosticsStream = null;

    /**
     * A string that gets prefixed to every message output by the logDiagnostic method, so that users can clearly see which AndroidLogFactory class is
     * generating the output.
     */
    private static String diagnosticPrefix;

    /**
     * A reference to the classloader that loaded this class. This is the same as AndroidLogFactory.class.getClassLoader(). However computing this
     * value isn't quite as simple as that, as we potentially need to use AccessControllers etc. It's more efficient to compute it once and cache it
     * here.
     */
    private static ClassLoader thisClassLoader;

    /**
     * The previously constructed <code>AndroidLogFactory</code> instances, keyed by the <code>ClassLoader</code> with which it was created.
     */
    protected static Map<ClassLoader, AndroidLogFactory> factories = null;

    // ----------------------------------------------------------- Constructors

    /**
     * Protected constructor that is not available for public use.
     */
    protected AndroidLogFactory() {
    }

    // --------------------------------------------------------- Public Methods

    static {
        // note: it's safe to call methods before initDiagnostics (though
        // diagnostic output gets discarded).
        thisClassLoader = getClassLoader(AndroidLogFactory.class);
        initDiagnostics();
        logClassLoaderEnvironment(AndroidLogFactory.class);
        factories = new HashMap<ClassLoader, AndroidLogFactory>();
        if (isDiagnosticsEnabled()) {
            logDiagnostic("BOOTSTRAP COMPLETED");
        }
    }

    /**
     * Return the configuration attribute with the specified name (if any), or <code>null</code> if there is no such attribute.
     * 
     * @param name
     *            Name of the attribute to return
     */
    public abstract Object getAttribute(String name);

    /**
     * Return an array containing the names of all currently defined configuration attributes. If there are no such attributes, a zero length array is
     * returned.
     */
    public abstract String[] getAttributeNames();

    /**
     * Convenience method to derive a name from the specified class and call <code>getInstance(String)</code> with it.
     * 
     * @param clazz
     *            Class for which a suitable Log name will be derived
     * 
     * @exception LogConfigurationException
     *                if a suitable <code>Log</code> instance cannot be returned
     */
    public abstract Log getInstance(Class<?> clazz) throws LogConfigurationException;

    /**
     * <p>
     * Construct (if necessary) and return a <code>Log</code> instance, using the factory's current set of configuration attributes.
     * </p>
     * 
     * <p>
     * <strong>NOTE</strong> - Depending upon the implementation of the <code>AndroidLogFactory</code> you are using, the <code>Log</code> instance
     * you are returned may or may not be local to the current application, and may or may not be returned again on a subsequent call with the same
     * name argument.
     * </p>
     * 
     * @param name
     *            Logical name of the <code>Log</code> instance to be returned (the meaning of this name is only known to the underlying logging
     *            implementation that is being wrapped)
     * 
     * @exception LogConfigurationException
     *                if a suitable <code>Log</code> instance cannot be returned
     */
    public abstract Log getInstance(String name) throws LogConfigurationException;

    /**
     * Release any internal references to previously created {@link Log} instances returned by this factory. This is useful in environments like
     * servlet containers, which implement application reloading by throwing away a ClassLoader. Dangling references to objects in that class loader
     * would prevent garbage collection.
     */
    public abstract void release();

    /**
     * Remove any configuration attribute associated with the specified name. If there is no such attribute, no action is taken.
     * 
     * @param name
     *            Name of the attribute to remove
     */
    public abstract void removeAttribute(String name);

    /**
     * Set the configuration attribute with the specified name. Calling this with a <code>null</code> value is equivalent to calling
     * <code>removeAttribute(name)</code>.
     * 
     * @param name
     *            Name of the attribute to set
     * @param value
     *            Value of the attribute to set, or <code>null</code> to remove any setting for this attribute
     */
    public abstract void setAttribute(String name, Object value);

    // --------------------------------------------------------- Static Methods

    /** Utility method to safely trim a string. */
    private static String trim(String src) {
        if (src == null) {
            return null;
        }
        return src.trim();
    }

    /**
     * <p>
     * Construct (if necessary) and return a <code>AndroidLogFactory</code> instance, using the following ordered lookup procedure to determine the
     * name of the implementation class to be loaded.
     * </p>
     * <ul>
     * <li>The <code>org.apache.commons.logging.AndroidLogFactory</code> system property.</li>
     * <li>The JDK 1.3 Service Discovery mechanism</li>
     * <li>Use the properties file <code>android-commons-logging.properties</code> file, if found in the class path of this class. The configuration
     * file is in standard <code>java.util.Properties</code> format and contains the fully qualified name of the implementation class with the key
     * being the system property defined above.</li>
     * <li>Fall back to a default implementation class (<code>org.apache.commons.logging.impl.AndroidLogFactoryImpl</code>).</li>
     * </ul>
     * 
     * <p>
     * <em>NOTE</em> - If the properties file method of identifying the <code>AndroidLogFactory</code> implementation class is utilized, all of the
     * properties defined in this file will be set as configuration attributes on the corresponding <code>AndroidLogFactory</code> instance.
     * </p>
     * 
     * <p>
     * <em>NOTE</em> - In a multithreaded environment it is possible that two different instances will be returned for the same classloader
     * environment.
     * </p>
     * 
     * @exception LogConfigurationException
     *                if the implementation class is not available or cannot be instantiated.
     */
    public static AndroidLogFactory getFactory() throws LogConfigurationException {
        // Return any previously registered factory for this class loader
        AndroidLogFactory factory = factories.get(thisClassLoader);
        if (factory != null) {
            return factory;
        }

        if (isDiagnosticsEnabled()) {
            logDiagnostic("[LOOKUP] AndroidLogFactory implementation requested for the first time for context classloader "
                    + objectId(thisClassLoader));
            logHierarchy("[LOOKUP] ", thisClassLoader);
        }

        // Load properties file.
        Properties props = getConfigurationFile(thisClassLoader, FACTORY_PROPERTIES);

        // Determine which concrete AndroidLogFactory subclass to use.
        // First, try a global system property
        if (isDiagnosticsEnabled()) {
            logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + "] to define the AndroidLogFactory subclass to use...");
        }

        try {
            String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
            if (factoryClass != null) {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] Creating an instance of AndroidLogFactory class '" + factoryClass + "' as specified by system property "
                            + FACTORY_PROPERTY);
                }

                factory = newFactory(factoryClass, thisClassLoader);
            } else {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
                }
            }
        } catch (SecurityException e) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": ["
                        + trim(e.getMessage()) + "]. Trying alternative implementations...");
            }
            ; // ignore
        } catch (RuntimeException e) {
            // This is not consistent with the behaviour when a bad AndroidLogFactory class is
            // specified in a services file.
            //
            // One possible exception that can occur here is a ClassCastException when
            // the specified class wasn't castable to this AndroidLogFactory type.
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + " instance of the custom factory class" + ": ["
                        + trim(e.getMessage()) + "] as specified by a system property.");
            }
            throw e;
        }

        // Try looking into the properties file read earlier (if found)

        if (factory == null) {
            if (props != null) {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY
                            + "' to define the AndroidLogFactory subclass to use...");
                }
                String factoryClass = props.getProperty(FACTORY_PROPERTY);
                if (factoryClass != null) {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("[LOOKUP] Properties file specifies AndroidLogFactory subclass '" + factoryClass + "'");
                    }
                    factory = newFactory(factoryClass, thisClassLoader);
                } else {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("[LOOKUP] Properties file has no entry specifying AndroidLogFactory subclass.");
                    }
                }
            } else {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] No properties file available to determine" + " AndroidLogFactory subclass from..");
                }
            }
        }

        // Try the fallback implementation class

        if (factory == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] Loading the default AndroidLogFactory implementation '" + FACTORY_DEFAULT
                        + "' via the same classloader that loaded this AndroidLogFactory" + " class (ie not looking in the context classloader).");
            }

            // Note: unlike the above code which can try to load custom AndroidLogFactory
            // implementations via the TCCL, we don't try to load the default AndroidLogFactory
            // implementation via the context classloader because:
            // * that can cause problems (see comments in newFactory method)
            // * no-one should be customising the code of the default class
            // Yes, we do give up the ability for the child to ship a newer
            // version of the AndroidLogFactoryImpl class and have it used dynamically
            // by an old AndroidLogFactory class in the parent, but that isn't 
            // necessarily a good idea anyway.
            factory = newFactory(FACTORY_DEFAULT, thisClassLoader);
        }

        if (factory != null) {
            // Always cache using context class loader.
            factories.put(thisClassLoader, factory);

            if (props != null) {
                for (Map.Entry<Object, Object> entry : props.entrySet()) {
                    String name = (String) entry.getKey();
                    String value = (String) entry.getValue();
                    factory.setAttribute(name, value);
                }
            }
        }

        return factory;
    }

    /**
     * Convenience method to return a named logger, without the application having to care about factories.
     * 
     * @param clazz
     *            Class from which a log name will be derived
     * 
     * @exception LogConfigurationException
     *                if a suitable <code>Log</code> instance cannot be returned
     */
    public static Log getLog(Class<?> clazz) throws LogConfigurationException {
        return (getFactory().getInstance(clazz));
    }

    /**
     * Convenience method to return a named logger, without the application having to care about factories.
     * 
     * @param name
     *            Logical name of the <code>Log</code> instance to be returned (the meaning of this name is only known to the underlying logging
     *            implementation that is being wrapped)
     * 
     * @exception LogConfigurationException
     *                if a suitable <code>Log</code> instance cannot be returned
     */
    public static Log getLog(String name) throws LogConfigurationException {
        return (getFactory().getInstance(name));
    }

    /**
     * Release any internal references to previously created {@link AndroidLogFactory} instances that have been associated with the specified class
     * loader (if any), after calling the instance method <code>release()</code> on each of them.
     * 
     * @param classLoader
     *            ClassLoader for which to release the AndroidLogFactory
     */
    public static void release(ClassLoader classLoader) {

        if (isDiagnosticsEnabled()) {
            logDiagnostic("Releasing factory for classloader " + objectId(classLoader));
        }
        synchronized (factories) {
            AndroidLogFactory factory = factories.get(classLoader);
            if (factory != null) {
                factory.release();
                factories.remove(classLoader);
            }
        }

    }

    /**
     * Release any internal references to previously created {@link AndroidLogFactory} instances, after calling the instance method
     * <code>release()</code> on each of them. This is useful in environments like servlet containers, which implement application reloading by
     * throwing away a ClassLoader. Dangling references to objects in that class loader would prevent garbage collection.
     */
    public static void releaseAll() {
        if (isDiagnosticsEnabled()) {
            logDiagnostic("Releasing factory for all classloaders.");
        }
        synchronized (factories) {
            for (AndroidLogFactory element : factories.values()) {
                element.release();
            }
            factories.clear();
        }
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Safely get access to the classloader for the specified class.
     * <p>
     * Theoretically, calling getClassLoader can throw a security exception, and so should be done under an AccessController in order to provide
     * maximum flexibility. However in practice people don't appear to use security policies that forbid getClassLoader calls. So for the moment all
     * code is written to call this method rather than Class.getClassLoader, so that we could put AccessController stuff in this method without any
     * disruption later if we need to.
     * <p>
     * Even when using an AccessController, however, this method can still throw SecurityException. Commons-logging basically relies on the ability to
     * access classloaders, ie a policy that forbids all classloader access will also prevent commons-logging from working: currently this method will
     * throw an exception preventing the entire app from starting up. Maybe it would be good to detect this situation and just disable all
     * commons-logging? Not high priority though - as stated above, security policies that prevent classloader access aren't common.
     * <p>
     * Note that returning an object fetched via an AccessController would technically be a security flaw anyway; untrusted code that has access to a
     * trusted JCL library could use it to fetch the classloader for a class even when forbidden to do so directly.
     * 
     * @since 1.1
     */
    protected static ClassLoader getClassLoader(Class<?> clazz) {
        try {
            return clazz.getClassLoader();
        } catch (SecurityException ex) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Unable to get classloader for class '" + clazz + "' due to security restrictions - " + ex.getMessage());
            }
            throw ex;
        }
    }

    /**
     * Return a new instance of the specified <code>AndroidLogFactory</code> implementation class, loaded by the specified class loader.
     * 
     * @param factoryClass
     *            Fully qualified name of the <code>AndroidLogFactory</code> implementation class
     * @param classLoader
     *            ClassLoader from which to load this class
     * @param contextClassLoader
     *            is the context that this new factory will manage logging for.
     * 
     * @exception LogConfigurationException
     *                if a suitable instance cannot be created
     * @since 1.1
     */
    protected static AndroidLogFactory newFactory(final String factoryClass, final ClassLoader classLoader) throws LogConfigurationException {
        // Note that any unchecked exceptions thrown by the createFactory
        // method will propagate out of this method; in particular a
        // ClassCastException can be thrown.
        Object result = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                return createFactory(factoryClass, classLoader);
            }
        });

        if (result instanceof LogConfigurationException) {
            LogConfigurationException ex = (LogConfigurationException) result;
            if (isDiagnosticsEnabled()) {
                logDiagnostic("An error occurred while loading the factory class:" + ex.getMessage());
            }
            throw ex;
        }
        if (isDiagnosticsEnabled()) {
            logDiagnostic("Created object " + objectId(result) + " to manage classloader " + objectId(classLoader));
        }
        return (AndroidLogFactory) result;
    }

    protected static Object createFactory(String factoryClass, ClassLoader classLoader) {
        // This will be used to diagnose bad configurations
        // and allow a useful message to be sent to the user
        Class<?> AndroidLogFactoryClass = null;
        try {
            if (classLoader != null) {
                // Warning: must typecast here & allow exception
                // to be generated/caught & recast properly.
                AndroidLogFactoryClass = classLoader.loadClass(factoryClass);
                if (AndroidLogFactory.class.isAssignableFrom(AndroidLogFactoryClass)) {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("Loaded class " + AndroidLogFactoryClass.getName() + " from classloader " + objectId(classLoader));
                    }
                } else {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("Factory class " + AndroidLogFactoryClass.getName() + " loaded from classloader "
                                + objectId(AndroidLogFactoryClass.getClassLoader()) + " does not extend '" + AndroidLogFactory.class.getName()
                                + "' as loaded by this classloader.");
                    }
                }

                return AndroidLogFactoryClass.newInstance();
            }
        } catch (Exception e) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Unable to create AndroidLogFactory instance.\n" + e);
            }
        }

        try {
            // Warning: must typecast here & allow exception
            // to be generated/caught & recast properly.
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Unable to load factory class via classloader " + objectId(classLoader)
                        + " - trying the classloader associated with this AndroidLogFactory.");
            }
            AndroidLogFactoryClass = Class.forName(factoryClass);
            return AndroidLogFactoryClass.newInstance();
        } catch (Exception e) {
            // Check to see if we've got a bad configuration
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Unable to create AndroidLogFactory instance.");
            }
            if (AndroidLogFactoryClass != null && !AndroidLogFactory.class.isAssignableFrom(AndroidLogFactoryClass)) {
                return new LogConfigurationException("The chosen AndroidLogFactory implementation does not extend AndroidLogFactory."
                        + " Please check your configuration.", e);
            }
            return new LogConfigurationException(e);
        }
    }

    /**
     * Given a filename, return an enumeration of URLs pointing to all the occurrences of that filename in the classpath.
     * <p>
     * This is just like ClassLoader.getResources except that the operation is done under an AccessController so that this method will succeed when
     * this jarfile is privileged but the caller is not. This method must therefore remain private to avoid security issues.
     * <p>
     * If no instances are found, an Enumeration is returned whose hasMoreElements method returns false (ie an "empty" enumeration). If resources
     * could not be listed for some reason, null is returned.
     */
    private static Enumeration<URL> getResources(final ClassLoader loader, final String name) {
        PrivilegedAction<Enumeration<URL>> action = new PrivilegedAction<Enumeration<URL>>() {
            public Enumeration<URL> run() {
                try {
                    if (loader != null) {
                        return loader.getResources(name);
                    } else {
                        return ClassLoader.getSystemResources(name);
                    }
                } catch (IOException e) {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("Exception while trying to find configuration file " + name + ":" + e.getMessage());
                    }
                    return null;
                } catch (NoSuchMethodError e) {
                    return null;
                }
            }
        };
        return AccessController.doPrivileged(action);
    }

    /**
     * Given a URL that refers to a .properties file, load that file. This is done under an AccessController so that this method will succeed when
     * this jarfile is privileged but the caller is not. This method must therefore remain private to avoid security issues.
     * <p>
     * Null is returned if the URL cannot be opened.
     */
    private static Properties getProperties(final URL url) {
        PrivilegedAction<Properties> action = new PrivilegedAction<Properties>() {
            public Properties run() {
                try {
                    InputStream stream = url.openStream();
                    if (stream != null) {
                        Properties props = new Properties();
                        props.load(stream);
                        stream.close();
                        return props;
                    }
                } catch (IOException e) {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("Unable to read URL " + url);
                    }
                }

                return null;
            }
        };
        return AccessController.doPrivileged(action);
    }

    /**
     * Locate a user-provided configuration file.
     * <p>
     * The classpath of the specified classLoader (usually the context classloader) is searched for properties files of the specified name. If none is
     * found, null is returned. If more than one is found, then the file with the greatest value for its PRIORITY property is returned. If multiple
     * files have the same PRIORITY value then the first in the classpath is returned.
     * <p>
     * This differs from the 1.0.x releases; those always use the first one found. However as the priority is a new field, this change is backwards
     * compatible.
     * <p>
     * The purpose of the priority field is to allow a webserver administrator to override logging settings in all webapps by placing a
     * android-commons-logging.properties file in a shared classpath location with a priority > 0; this overrides any
     * android-commons-logging.properties files without priorities which are in the webapps. Webapps can also use explicit priorities to override a
     * configuration file in the shared classpath if needed.
     */
    private static final Properties getConfigurationFile(ClassLoader classLoader, String fileName) {

        Properties props = null;
        double priority = 0.0;
        URL propsUrl = null;
        try {
            Enumeration<URL> urls = getResources(classLoader, fileName);

            if (urls == null) {
                return null;
            }

            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();

                Properties newProps = getProperties(url);
                if (newProps != null) {
                    if (props == null) {
                        propsUrl = url;
                        props = newProps;
                        String priorityStr = props.getProperty(PRIORITY_KEY);
                        priority = 0.0;
                        if (priorityStr != null) {
                            priority = Double.parseDouble(priorityStr);
                        }

                        if (isDiagnosticsEnabled()) {
                            logDiagnostic("[LOOKUP] Properties file found at '" + url + "'" + " with priority " + priority);
                        }
                    } else {
                        String newPriorityStr = newProps.getProperty(PRIORITY_KEY);
                        double newPriority = 0.0;
                        if (newPriorityStr != null) {
                            newPriority = Double.parseDouble(newPriorityStr);
                        }

                        if (newPriority > priority) {
                            if (isDiagnosticsEnabled()) {
                                logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + " with priority " + newPriority + " overrides file at '"
                                        + propsUrl + "'" + " with priority " + priority);
                            }

                            propsUrl = url;
                            props = newProps;
                            priority = newPriority;
                        } else {
                            if (isDiagnosticsEnabled()) {
                                logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + " with priority " + newPriority
                                        + " does not override file at '" + propsUrl + "'" + " with priority " + priority);
                            }
                        }
                    }

                }
            }
        } catch (SecurityException e) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("SecurityException thrown while trying to find/read config files.");
            }
        }

        if (isDiagnosticsEnabled()) {
            if (props == null) {
                logDiagnostic("[LOOKUP] No properties file of name '" + fileName + "' found.");
            } else {
                logDiagnostic("[LOOKUP] Properties file of name '" + fileName + "' found at '" + propsUrl + '"');
            }
        }

        return props;
    }

    /**
     * Read the specified system property, using an AccessController so that the property can be read if JCL has been granted the appropriate security
     * rights even if the calling code has not.
     * <p>
     * Take care not to expose the value returned by this method to the calling application in any way; otherwise the calling app can use that info to
     * access data that should not be available to it.
     */
    private static String getSystemProperty(final String key, final String def) throws SecurityException {
        return AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty(key, def);
            }
        });
    }

    /**
     * Determines whether the user wants internal diagnostic output. If so, returns an appropriate writer object. Users can enable diagnostic output
     * by setting the system property named {@link #DIAGNOSTICS_DEST_PROPERTY} to a filename, or the special values STDOUT or STDERR.
     */
    private static void initDiagnostics() {
        String dest;
        try {
            dest = getSystemProperty(DIAGNOSTICS_DEST_PROPERTY, null);
            if (dest == null) {
                return;
            }
        } catch (SecurityException ex) {
            // We must be running in some very secure environment.
            // We just have to assume output is not wanted..
            return;
        }

        if (dest.equals("STDOUT")) {
            diagnosticsStream = System.out;
        } else if (dest.equals("STDERR")) {
            diagnosticsStream = System.err;
        } else {
            try {
                // open the file in append mode
                FileOutputStream fos = new FileOutputStream(dest, true);
                diagnosticsStream = new PrintStream(fos);
            } catch (IOException ex) {
                // We should report this to the user - but how?
                return;
            }
        }

        String classLoaderName;
        try {
            if (thisClassLoader == null) {
                classLoaderName = "BOOTLOADER";
            } else {
                classLoaderName = objectId(thisClassLoader);
            }
        } catch (SecurityException e) {
            classLoaderName = "UNKNOWN";
        }
        diagnosticPrefix = "[AndroidLogFactory from " + classLoaderName + "] ";
    }

    protected static boolean isDiagnosticsEnabled() {
        return diagnosticsStream != null;
    }

    private static final void logDiagnostic(String msg) {
        if (diagnosticsStream != null) {
            diagnosticsStream.print(diagnosticPrefix);
            diagnosticsStream.println(msg);
            diagnosticsStream.flush();
        }
    }

    protected static final void logRawDiagnostic(String msg) {
        if (diagnosticsStream != null) {
            diagnosticsStream.println(msg);
            diagnosticsStream.flush();
        }
    }

    /**
     * Generate useful diagnostics regarding the classloader tree for the specified class.
     * <p>
     * As an example, if the specified class was loaded via a webapp's classloader, then you may get the following output:
     * 
     * <pre>
     * Class com.acme.Foo was loaded via classloader 11111
     * ClassLoader tree: 11111 -> 22222 (SYSTEM) -> 33333 -> BOOT
     * </pre>
     * <p>
     * This method returns immediately if isDiagnosticsEnabled() returns false.
     * 
     * @param clazz
     *            is the class whose classloader + tree are to be output.
     */
    private static void logClassLoaderEnvironment(Class<?> clazz) {
        if (!isDiagnosticsEnabled()) {
            return;
        }

        try {
            // Deliberately use System.getProperty here instead of getSystemProperty; if
            // the overall security policy for the calling application forbids access to
            // these variables then we do not want to output them to the diagnostic stream. 
            logDiagnostic("[ENV] Extension directories (java.ext.dir): " + System.getProperty("java.ext.dir"));
            logDiagnostic("[ENV] Application classpath (java.class.path): " + System.getProperty("java.class.path"));
        } catch (SecurityException ex) {
            logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths.");
        }

        String className = clazz.getName();
        ClassLoader classLoader;

        try {
            classLoader = getClassLoader(clazz);
        } catch (SecurityException ex) {
            // not much useful diagnostics we can print here!
            logDiagnostic("[ENV] Security forbids determining the classloader for " + className);
            return;
        }

        logDiagnostic("[ENV] Class " + className + " was loaded via classloader " + objectId(classLoader));
        logHierarchy("[ENV] Ancestry of classloader which loaded " + className + " is ", classLoader);
    }

    /**
     * Logs diagnostic messages about the given classloader and it's hierarchy. The prefix is prepended to the message and is intended to make it
     * easier to understand the logs.
     * 
     * @param prefix
     * @param classLoader
     */
    private static void logHierarchy(String prefix, ClassLoader classLoader) {
        if (!isDiagnosticsEnabled()) {
            return;
        }
        ClassLoader systemClassLoader;
        if (classLoader != null) {
            final String classLoaderString = classLoader.toString();
            logDiagnostic(prefix + objectId(classLoader) + " == '" + classLoaderString + "'");
        }

        try {
            systemClassLoader = ClassLoader.getSystemClassLoader();
        } catch (SecurityException ex) {
            logDiagnostic(prefix + "Security forbids determining the system classloader.");
            return;
        }
        if (classLoader != null) {
            StringBuffer buf = new StringBuffer(prefix + "ClassLoader tree:");
            for (;;) {
                buf.append(objectId(classLoader));
                if (classLoader == systemClassLoader) {
                    buf.append(" (SYSTEM) ");
                }

                try {
                    classLoader = classLoader.getParent();
                } catch (SecurityException ex) {
                    buf.append(" --> SECRET");
                    break;
                }

                buf.append(" --> ");
                if (classLoader == null) {
                    buf.append("BOOT");
                    break;
                }
            }
            logDiagnostic(buf.toString());
        }
    }

    /**
     * Returns a string that uniquely identifies the specified object, including its class.
     * <p>
     * The returned string is of form "classname@hashcode", ie is the same as the return value of the Object.toString() method, but works even when
     * the specified object's class has overidden the toString method.
     * 
     * @param o
     *            may be null.
     * @return a string of form classname@hashcode, or "null" if param o is null.
     */
    public static String objectId(Object o) {
        if (o == null) {
            return "null";
        } else {
            return o.getClass().getName() + "@" + System.identityHashCode(o);
        }
    }
}
